using System;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Collections.Specialized;
using System.Configuration;
using System.Text.RegularExpressions;

namespace GsmLink.Comm
{
	/// <summary>
	/// Defines the properties and methods that must be implemented by a 
	/// <b>communication driver</b>.
	/// </summary>
	/// <remarks>
	/// A <b>communication driver</b> provides the mechanism for interfacing 
	/// with a GSM device through a specific low-level communication protocol  
	/// such as <b>RS232</b>, <b>IrDA</b>, <b>Bluetooth</b>, etc. A <b>communication
	/// driver</b> typically encapsulates a single low-level communication protocol. 
	/// <para>
	/// The <see cref="GsmDevice"/> class uses an instance of a class that 
	/// implements the <b>ICommDriver</b> interface to send and receive AT commands 
	/// to the GSM device.
	/// </para>
	/// <para>
	/// Note: While it is possible to create a <b>communication driver</b> by
	/// creating a class that implements the properties and methods of the 
	/// <b>ICommDriver</b> interface, it is recommended that developers derive 
	/// a new class from the <see cref="CommDriver"/> abstract class instead. 
	/// This is because the <see cref="CommDriver"/> class already has implementations
	/// of most of <b>ICommDriver</b>'s properties and methods.	
	/// </para>
	/// </remarks>
	public interface ICommDriver  
	{		
		string ConfigFile { get; }
		string ProfileName { get; }			
		bool HasConfigDialogBox { get; }
		bool HasOwnConfigFile { get; }
		bool IsConfigured { get; }
		bool IsStarted { get; }				
		
		void Configure(string configFile, string profileName);				
		void ShowConfigDialogBox();
		void Start();		
		void Stop();		
		bool SendCommand(string command);
		string GetResponse(string[] endTokens, TimeSpan timeout);				
		string Transact(string command, string[] endTokens, TimeSpan timeout);
		string Transact(string[] commands, string[][] endTokens, string[] successTokens, out string[] responses, 
			TimeSpan pauseTime, TimeSpan timeout);

	}

	/// <summary>
	/// Provides a template for creating a <b>communication driver</b>. 
	/// </summary>
	/// <remarks>
	/// The <b>CommDriver</b> class provides a basic implementation of 
	/// the properties and methods defined by the <see cref="ICommDriver"/>
	/// interface. It is intended to serve as the base class for creating 
	/// a <b>communication driver.</b>.
	/// </remarks>
	public abstract class CommDriver : ICommDriver
	{
		#region Fields
		
		protected bool isConfigured = false;		
		protected bool isStarted = false;
		private string profileName = "";		
		private string configFile = "";
		private NameValueCollection settings = new NameValueCollection();				

		#endregion Fields

		#region Constructor	
	
		public CommDriver()
		{
		}		

		#endregion

		#region Properties

		public virtual string ConfigFile
		{
			get { return this.configFile; }				
		}
		
		public virtual string ProfileName
		{
			get { return this.profileName; }						
		}	

		public virtual bool HasConfigDialogBox 
		{ 
			get { return false; }
		}

		public virtual bool HasOwnConfigFile
		{
			get { return false; }
		}

		public virtual bool IsConfigured
		{
			get { return this.isConfigured; }
		}

		public virtual bool IsStarted
		{
			get { return this.isStarted; }
		}		

		protected virtual NameValueCollection Settings
		{
			get { return this.settings; }
		}
		
		#endregion Properties

		#region Methods
		
		public virtual void Configure(string configFile, string profileName)
		{
			if (!this.isStarted)
			{
				// Validate and set the values of the configFile
				// and profileName.
				SetConfigFile(configFile);		
				SetProfileName(profileName);
			
				// Check first if the configFile is still blank. 
				// If so, then check if a derived class has overriden 
				// the ConfigFile property; if yes then get the 
				// value of the property, validate it and then use
				// it as the value of configFile.
				if (this.configFile == "")
				{
					string configFile1 = ConfigFile;
					SetConfigFile(configFile1);
				}
			
				if (this.configFile != "")
				{			
					// Load the config file and extract the settings.			
					
					// TODO: Use XPathDocument, XPathNavigator and NameValueSectionHandler 
					// objects to do the task below:

					try
					{				
						this.settings.Clear();
						
						XmlTextReader reader = new XmlTextReader(this.configFile);									
						while (reader.Read())
						{
							if (reader.NodeType == XmlNodeType.Element && reader.Name == "profile")
							{
								if (reader["name"] == profileName)
								{
									while(reader.Read())
									{
										if (reader.NodeType == XmlNodeType.Element && reader.Name == "add")
											this.settings.Add(reader["key"], reader["value"]);
									}									
									
									break;
								}						
							}				
						}								
						
						reader.Close();						
					}
					catch (Exception ex)
					{
						string errorMsg = "An error occurred while loading config file: " + this.configFile;
						throw new ApplicationException(errorMsg + "\n" + ex.Message);
					}
				}
			}
			else
			{
				string errorMsg = "Cannot invoke the Configure method on the CommDriver instance " +
					"because it is already started.";
				throw new ApplicationException(errorMsg);			
			}
		}
	
		private void SetConfigFile(string configFile)
		{
			if (!this.isStarted)
			{
				if (configFile != null)
				{
					this.configFile = configFile;
					if (this.configFile != "" && !File.Exists(configFile))
					{
						string errorMsg = "The configuration file '" + configFile +	"' does not exist.";
						throw new ApplicationException(errorMsg);
					}
				}
				else
				{
					string errorMsg = "The ConfigFile property cannot be null.";
					throw new ApplicationException(errorMsg);
				}
			}
			else
			{
				string errorMsg = "The configFile field cannot be modified because " +
					"the CommDriver instance has already been started.";
				throw new ApplicationException(errorMsg);
			}			
		}

		private void SetProfileName(string profileName)
		{
			if (!this.isStarted)
			{
				if (profileName != null && profileName != "")
					this.profileName = profileName;
				else
				{
					string errorMsg = "The ProfileName property cannot be null or blank.";
					throw new ApplicationException(errorMsg);
				}
			}
			else
			{
				string errorMsg = "The profileName field cannot be modified because " +
					"the CommDriver instance has already been started.";
				throw new ApplicationException(errorMsg);
			}			
		}
			
		public virtual void ShowConfigDialogBox()
		{
		}

		public abstract void Start();		
		public abstract void Stop();
		public abstract bool SendCommand(string command);
		public abstract string GetResponse(string[] endTokens, TimeSpan timeout);				
		public abstract string Transact(string command, string[] endTokens, TimeSpan timeout);
		
		public virtual string Transact(string[] commands, string[][] endTokens, string[] successTokens, out string[] responses, TimeSpan pauseTime, TimeSpan timeout)
		{
			throw new NotSupportedException("This method is not supported.");
		}

		#endregion Methods

	}

	/// <summary>
	/// Manages the creation of <b>communication drivers</b>
	/// for use by the <see cref="GsmDevice"/> class.
	/// </summary>
	/// <remarks>
	/// A <b>communication driver</b> is a class that implements 
	/// the <see cref="ICommDriver"/> interface. 
	/// <para>
	/// The <b>CommDriverFactory</b> class is responsible for hiding away 
	/// the details of creating a <b>communication driver</b>, each of which 
	/// may use a different communication interface - such as serial, infrared,
	/// bluetooth, etc. - to communicate with the physical GSM device.
	/// </para>
	/// </remarks>	
	public sealed class CommDriverFactory
	{
		#region Fields

		private const string DEFAULT_CONFIG_FILE = "CommDriverFactory.config";
		private static string configFile = DEFAULT_CONFIG_FILE;
		private static bool isConfigured = false;
		
		#endregion Fields

		#region Constructors

		// Don't allow the creation of this class.
		private CommDriverFactory()
		{			
		}

		static CommDriverFactory()
		{
			if (File.Exists(DEFAULT_CONFIG_FILE))
				Configure(DEFAULT_CONFIG_FILE);			
		}

		#endregion Constructors

		#region Properties

		public static string ConfigFile
		{
			get { return configFile; }			
		}

		public static bool IsConfigured
		{
			get { return isConfigured; }
		}

		#endregion Properties

		#region Methods

		public static void Configure()
		{
			Configure(configFile);
		}

		public static void Configure(string configFile)
		{
			isConfigured = false;
			SetConfigFile(configFile);			
			
			// TODO: Read in the configuration file.
			// Load the commDrivers and commDriverProfiles,
			// and store each of them in a suitable data 
			// structure, then put them into separate 
			// collection objects.

			isConfigured = true;
		}

		private static void SetConfigFile(string cfgfile)
		{			
			if (cfgfile != null && cfgfile != "")
			{
				configFile = cfgfile;
				
				// Check if the config file does not exist.
				if (!File.Exists(configFile))
				{
					string errorMsg = "The configuration file '" + configFile + "' does not exist.";
					throw new ApplicationException(errorMsg);
				}
			}
			else
			{
				string errorMsg = "The configFile specified is null or blank.";
				throw new ApplicationException(errorMsg);
			}			
		}		

		/// <summary>
		/// Creates a <b>communication driver</b> using the specified
		/// profile.
		/// </summary>
		/// <param name="profileName">
		/// The profile of the <b>communication driver</b> to create.
		/// </param>
		/// <remarks>
		/// The <b>GetCommDriver</b> method accepts the following 
		/// "special" profiles: "COM1", "COM2", "COM3", and "COM4";
		/// these indicate that an instance of the <see cref="SerialCommDriver"/>
		/// will be created and configured using the specified
		/// port names.
		/// </remarks>
		/// <returns>
		/// An <see cref="ICommDriver"/> object is successful; <b>null</b> 
		/// otherwise.
		/// </returns>
		public static ICommDriver GetCommDriver(string profileName)
		{
			ICommDriver commDriver = null;

			if (Regex.IsMatch(profileName.ToUpper(), "COM[1-8]"))
			{
				commDriver = new SerialCommDriver("", profileName.ToUpper());				
			}
			else
			{
				// TODO:
				// 1. Locate the profile in the collection of commDriverProfiles.
				// 2. Using reflection: (a) load the assembly containing 
				//    the CommDriver; (b) create an instance of the class
				//    specified in the profile.
				// 3. Cast the instance to the ICommDriver interface.
				// 4. Invoke the Configure method on the instance.
				//    if (commDriver.HasOwnConfigFile)
				//      commDriver.Configure("", profileName)
				//    else 
				//      commDriver.Configure(configFile, profileName);	
				// throw new NotImplementedException("This method is not yet implemented.");
			}			

			return commDriver;
		}		

		#endregion Methods

	}

	
}
